package io.lvdaxian.mybatis05.builder.xml;

import io.lvdaxian.mybatis05.builder.BaseBuilder;
import io.lvdaxian.mybatis05.datasource.DataSourceFactory;
import io.lvdaxian.mybatis05.io.Resources;
import io.lvdaxian.mybatis05.mapping.BoundSql;
import io.lvdaxian.mybatis05.mapping.Environment;
import io.lvdaxian.mybatis05.mapping.MappedStatement;
import io.lvdaxian.mybatis05.mapping.SqlCommandType;
import io.lvdaxian.mybatis05.session.Configuration;
import io.lvdaxian.mybatis05.transaction.TransactionFactory;
import io.lvdaxian.mybatis05.type.TypeAliasRegistry;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 此方法是 xml config 构建者
 *
 * @author lihh
 */
public class XMLConfigBuilder extends BaseBuilder {
  
  private final Element root;
  
  public XMLConfigBuilder(Reader reader) {
    // 初期化的时候 实例化 Configuration
    super(new Configuration());
    
    // dom4j 处理 xml
    SAXReader saxReader = new SAXReader();
    try {
      // 解析 入口的reader
      Document document = saxReader.read(new InputSource(reader));
      // 拿到 根标签
      root = document.getRootElement();
    } catch (DocumentException e) {
      throw new RuntimeException(e);
    }
  }
  
  public Configuration parse() {
    try {
      // 解析 数据源标签
      environmentsElement(root.element("environments"));
      
      // 这里开始 解析总的标签【mappers】
      mapperElement(root.element("mappers"));
    } catch (IOException | DocumentException | InstantiationException |
             IllegalAccessException e) {
      throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
    return configuration;
  }
  
  /**
   * 解析 环境标签的方法
   *
   * @param context 解析environments 根标签
   * @author lihh
   */
  private void environmentsElement(Element context) throws InstantiationException, IllegalAccessException {
    // 从这里 获取到默认值, 这是默认激活的的数据源
    String environment = context.attributeValue("default");
    // 筛选 多数据源 从 xml 配置中解析出 environment
    List<Element> environmentList = context.elements("environment");
    // 从配置中 拿到 类型别名注册器
    TypeAliasRegistry typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    
    // 遍历 多个环境配置
    for (Element e : environmentList) {
      String id = e.attributeValue("id");
      
      // 表示 筛选到了 默认设置的环境
      if (!environment.equals(id))
        continue;
      
      // 拿到事务工厂
      TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(e.element("transactionManager").attributeValue("type")).newInstance();
      
      // 从 这里拿到数据源 Element标签
      Element dataSourceElement = e.element("dataSource");
      // 数据源工厂, 这里数据源工厂 已经注册到 typeAliasRegistry 别名集合中了
      DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(
              dataSourceElement.attributeValue("type")
          )
          .newInstance();
      // 拿到属性[property] 原则上是可以拿到 多个property
      List<Element> propertyList = dataSourceElement.elements("property");
      
      Properties props = new Properties();
      propertyList.forEach(el -> {
        String name = el.attributeValue("name"),
            value = el.attributeValue("value");
        
        props.setProperty(name, value);
      });
      // 给 数据源工厂 设置属性
      dataSourceFactory.setProperties(props);
      // 拿到数据源
      DataSource dataSource = dataSourceFactory.getDataSource();
      
      // 构建 数据源环境
      Environment.Builder environmentBuilder = new Environment
          .Builder(id)
          .transactionFactory(txFactory)
          .dataSource(dataSource);
      
      // 配置文件 设置环境
      configuration.setEnvironment(environmentBuilder.build());
    }
  }
  
  /**
   * 从这里开始 处理 mapper element
   *
   * @param mappers element 元素
   * @author lihh
   */
  private void mapperElement(Element mappers) throws IOException, DocumentException {
    // 从这里解析单个 mapper
    List<Element> mapperList = mappers.elements("mapper");
    
    for (Element e : mapperList) {
      // 从 mapper 中拿到 resource
      String resource = e.attributeValue("resource");
      // 读取mapper中 resource的资源
      Reader reader = Resources.getResourceAsReader(resource);
      
      SAXReader saxReader = new SAXReader();
      // 从这里读取 root 标签
      Document document = saxReader.read(new InputSource(reader));
      Element root = document.getRootElement();
      
      //命名空间
      String namespace = root.attributeValue("namespace");
      
      // SELECT
      List<Element> selectNodes = root.elements("select");
      for (Element node : selectNodes) {
        // 从 select 中获取 特定的属性
        String id = node.attributeValue("id");
        String parameterType = node.attributeValue("parameterType");
        String resultType = node.attributeValue("resultType");
        String sql = node.getText();
        
        // ? 匹配
        Map<Integer, String> parameter = new HashMap<>();
        Pattern pattern = Pattern.compile("(#\\{(.*?)})");
        // 从这里 match 到数据
        Matcher matcher = pattern.matcher(sql);
        
        // 从这里 遍历match的值
        for (int i = 1; matcher.find(); i++) {
          String g1 = matcher.group(1);
          String g2 = matcher.group(2);
          parameter.put(i, g2);
          // #{} 替换为 ? 但是将 g2 保存下来，方便后续的替换
          sql = sql.replace(g1, "?");
        }
        
        // 由 namespace 以及 主键id 构建唯一的id
        String msId = namespace + "." + id;
        String nodeName = node.getName();
        // 表示 sql命令类型
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        BoundSql boundSql = new BoundSql(sql, parameter, parameterType, resultType);
        // 构建 statement 命令
        MappedStatement mappedStatement = new MappedStatement.Builder(configuration, msId, sqlCommandType, boundSql).build();
        
        // 添加 解析的sql
        configuration.addMappedStatement(mappedStatement);
      }
      
      // 根据namespace 添加 mapper 映射器
      configuration.addMapper(Resources.classForName(namespace));
    }
  }
}
